#include "rosterelements.h"

#include "other/unit.h"
#include "roster.h"
#include "columnheader.h"
#include "other/objectactions.h"
#include "collections/unitcollection.h"

#include <QStringBuilder>
#include <QDebug>

#define NAME_COLUMN 0
#define SIZE_COLUMN 1
#define COST_COLUMN header->count()+2

Roster* RosterElement::currentRoster = NULL;
const DualCollection<GameItem>* RosterUnit::_items = NULL;
const UnitCollection* RosterUnit::_units = NULL;

RosterElement::RosterElement(RosterElement *_parent) :
    yALB_AbstractTreeItem(_parent) {
    if(_parent && currentRoster) {
        currentRoster->aboutToAddChild(_parent);
        currentRoster->childAdded();
    }
}

RosterElement::~RosterElement() {
    if(currentRoster && parentItem) {
        currentRoster->aboutToRemoveChildren(this, 0, childCount());
        currentRoster->childrenRemoved();
    }
}

QVariant RosterElement::text(int column, const ColumnHeader *header) const {
    Q_UNUSED(header)
    if(column == NAME_COLUMN)
        return _name;
    return QVariant();
}

QVariant RosterElement::userData(int column) const {
    Q_UNUSED(column)
    return _key;
}

RosterElement::ElementType RosterElement::type() const {
    return Element;
}

void RosterElement::addChild(RosterElement *child) {
    if(child) {
        children.append(child);
        child->parentItem = this;
        if(currentRoster && child->type() == RosterElement::RosterUnitType)
            currentRoster->emitUnitAdded(static_cast<RosterUnit*>(child));
    } else {
        qDebug() << "Cannot add child for" << this->_name;
    }
}

void RosterElement::removeChild(RosterElement *child) {
    children.remove(children.indexOf(child));
}

RosterElement* RosterElement::child(int index) const {
    return static_cast<RosterElement*>(yALB_AbstractTreeItem::child(index));
}

RosterElement* RosterElement::parent() const {
    // Dynamic cast because rootItem is not of type RosterElement*
    return dynamic_cast<RosterElement*>(yALB_AbstractTreeItem::parent());
}

void RosterElement::setName(const QString &newName) {
    _name = newName;
}

QString RosterElement::name() const {
    return _name;
}

QString RosterElement::key() const {
    return _key;
}

void RosterElement::setCurrentRoster(Roster* roster) { // Static function
    RosterElement::currentRoster = roster;
}

RosterUnit::RosterUnit(const QString& _unitKey, RosterElement *parent) :
    RosterElement(parent) {
    Q_ASSERT(_units);

    _key = _unitKey;
    replacesModel = false;

    // \NOTE: Using get() will cause the unit to be copied
    Unit unit(_units->get(_unitKey));

    _name = unit.getName();
    costPerModel = unit.getCost();
    size = unit.getMinimumSize();
    cost = costPerModel * size;
    cells = unit.cells;

    // Initialize each allowed item as 1 in allowedItems QHash
    for(int i = 0; i<unit.allowedItems.size(); ++i) {
        const QString& v = unit.allowedItems.at(i);
        if(v.startsWith(QChar('!'))) {
            QString item(v.mid(1));
            allowedItems[item] = allowedItems.value(item, 1) - 1;
        } else
            ++allowedItems[v];
    }

    itemLimits = unit.itemLimits;
    maximumSize = unit.getMaximumSize();

    // Fill visible options

    QStringList optionKeys = unit.options.keys();
    for(int i = 0; i<optionKeys.size(); ++i) {
        UnitOption::InitialState state = unit.options.getRealElement(optionKeys.at(i))->state;
        if(state == UnitOption::Invisible || state == UnitOption::EnabledInvisible)
            optionVisibleStatus[optionKeys.at(i)] = 0; // Default to invisible
        else
            optionVisibleStatus[optionKeys.at(i)] = 1; // Default to visible

        if(state == UnitOption::EnabledLocked || state == UnitOption::Locked)
            optionLockedStatus[optionKeys.at(i)] = 1; // Default to locked
        else
            optionLockedStatus[optionKeys.at(i)] = 0; // Default to unlocked
    }
    if(currentRoster)
        currentRoster->amountPoints(cost);
}

RosterUnit::~RosterUnit() {
    if(currentRoster) // \NOTE: Requires testing
        currentRoster->emitUnitAboutToBeRemoved(const_cast<RosterUnit*>(this));

    // Remove all items
    for(auto i = activatedOptions.constBegin(); i != activatedOptions.constEnd(); ++i) {
        // \TODO: Option removal handled manually. Events should be invoked manually too
    }
    if(_items) {
        for(auto i = itemsInUnit.constBegin(); i != itemsInUnit.constEnd(); ++i) {
            // \TODO: Item removal handled manually. Events should be invoked manually too
        }
    }
    if(currentRoster)
        currentRoster->amountPoints(0.0-cost);
}

Unit RosterUnit::getBaseUnit() const {
    Q_ASSERT(_units);
    return _units->get(_key);
}

QVariant RosterUnit::text(int column, const ColumnHeader *header) const {
    if(column == NAME_COLUMN)
        return _name;
    else if(column == SIZE_COLUMN)
        return size;
    else if(column == COST_COLUMN)
        return cost;
    else {
        HeaderColumn c(header->at(column -2));
        QString ret = cells.value(c.key, c.column->defaultValueAs);
        if(ret == c.column->defaultValue)
            ret = c.column->defaultValueAs;
        return ret;
    }
}

RosterElement::ElementType RosterUnit::type() const {
    return RosterElement::RosterUnitType;
}

uint RosterUnit::getSize() const {
    return size;
}

void RosterUnit::setSize(uint size) {
    if(size < 1 || size == this->size)
        return;
    cost += (size - this->size) * costPerModel;
    if(currentRoster)
        currentRoster->emitUnitSizeChanged(const_cast<RosterUnit*>(this));
}

uint RosterUnit::getMaximumSize() const {
    return maximumSize;
}

void RosterUnit::setMaximumSize(uint size) {
    maximumSize = size;
}

float RosterUnit::getCostPerModel() const {
    return costPerModel;
}

void RosterUnit::setCostPerModel(float cost) {
    float oldCost = this->cost;
    this->cost += (cost - costPerModel) * size;
    costPerModel = cost;
    if(currentRoster)
        currentRoster->emitUnitCostChanged(const_cast<RosterUnit*>(this), oldCost);
}

float RosterUnit::getCost() const {
    return cost;
}

void RosterUnit::amountCost(float cost) {
    float oldCost = this->cost;
    this->cost += cost;
    if(currentRoster)
        currentRoster->emitUnitCostChanged(const_cast<RosterUnit*>(this), oldCost);
}

bool RosterUnit::isAllowedItem(const QString& key, bool checkCategory) const {
    // Returns true if the unit may add the item.
    if(checkCategory) {
        GameItem *item = _items->getRealElement(key);
        if(item == NULL)
            return false;
        if(!item->category.isEmpty())
            return (allowedItems.value(">" % item->category) + allowedItems.value(key)) > 0;
    }
    return allowedItems.value(key) > 0;
}

void RosterUnit::addAllowedItems(const QStringList& value) {
    for(int i = 0; i<value.size(); ++i) {
        const QString& v = value.at(i);
        if(v.startsWith(QChar('!'))) {
            QString item(v.mid(1));
            allowedItems[item] = allowedItems.value(item, 1) - 1;
        } else
            ++allowedItems[v];
    }
}

void RosterUnit::subtractAllowedItems(const QStringList& value) {
    for(int i = 0; i<value.size(); ++i) {
        const QString& v = value.at(i);
        if(v.startsWith(QChar('!'))) {
            QString item(v.mid(1));
            ++allowedItems[item];
        } else
            allowedItems[v] = allowedItems.value(v, 1) - 1;
    }
    deleteNonAllowedItems();
}

QStringList RosterUnit::getItemsWithAllowedInf() const {
    return allowedItems.keys();
}

UnitItems_t RosterUnit::getItemsInUnit() const {
    return itemsInUnit;
}

bool RosterUnit::containsItem(const QString &itemKey) const {
    return itemsInUnit.value(itemKey,0) > 0;
}

bool RosterUnit::addItem(const QString& itemKey, quint8 number) {
    // \TODO: Consider making a class that checks category limits

    if(!_items)
        return false;
    GameItem *item = _items->getRealElement(itemKey);
    if(!isAllowedItem(itemKey))
        return false;
    if(number <= 0)
        return false;

    // Limit tests
    // Cannot exceed the item maximum number
    if(item->getMaximumNumber() > (quint8)0 && itemsInUnit.value(itemKey, 0) + number > item->getMaximumNumber())
        return false;

    InternalData &data = categoryData[item->category];
    InternalData &globalData = categoryData[""];
    InheritedCategoryData limits = itemLimits.value(item->category);
    uint numericLimit, percentageLimit, pointLimit;
    float percentagePoints, usedPoints;
    uint itemCount;
    bool isEmptyCategory = item->category.isEmpty();

    // Check if we should use global limits or category-specific limits
    percentagePoints = data.points;
    if(!isEmptyCategory) {
        InheritedCategoryData globalLimits = itemLimits.value("");

        if(limits.numericLimit > 0) {
            numericLimit = limits.numericLimit;
            itemCount = data.itemCount;
        } else {
            numericLimit = globalLimits.numericLimit;
            itemCount = globalData.itemCount;
        }
        if(limits.percentageLimit > 0) {
            percentageLimit = limits.percentageLimit;
        } else {
            percentageLimit = globalLimits.percentageLimit;
        }

        if(limits.pointLimit > 0) {
            pointLimit = limits.pointLimit;
            usedPoints = data.points;
        } else {
            pointLimit = globalLimits.pointLimit;
            usedPoints = globalData.points;
        }
    } else {
        numericLimit = limits.numericLimit;
        itemCount = data.itemCount;
        percentageLimit = limits.percentageLimit;
        pointLimit = limits.pointLimit;
        usedPoints = data.points;
    }


    // Cannot exceed the numeric limit of this category
    if(numericLimit > 0 && itemCount + number > numericLimit)
        return false;

    // \TODO: Improve algorythm

    float points = number *item->getCost();

    // Cannot exceed the percentage limit of this category (100% is the total USED points)
    if(percentageLimit > 0 && !isEmptyCategory && globalData.points > 0 &&
            (percentagePoints + points) *100/globalData.points > percentageLimit)
        return false;

    // Cannot exceed the point limit of this category
    if(pointLimit > 0 && usedPoints + points > pointLimit)
        return false;

    itemsInUnit[itemKey] += number;
    data.itemCount += number;
    data.points += points;
    if(!isEmptyCategory) {
        globalData.itemCount += number;
        globalData.points += points;
    }
    cost += points;
    if(currentRoster) {
        currentRoster->amountPoints(points);
        currentRoster->emitItemAdded(const_cast<RosterUnit*>(this), itemKey, number);
    }
    return true;
}

bool RosterUnit::subtractItem(const QString& itemKey, quint8 number) {
    if(!_items)
        return false;
    if(number <= 0 || !itemsInUnit.contains(itemKey))
        return false;
    GameItem *item = _items->getRealElement(itemKey);
    if(!item)
        return false;
    InternalData &data = categoryData[item->category];
    InternalData &globalData = categoryData[""];
    number = qMin<uint>(number, itemsInUnit[itemKey]);
    float points = number * item->getCost();

    itemsInUnit[itemKey] -= number;
    data.itemCount -= number;
    data.points -= points;
    if(!item->category.isEmpty()) {
        globalData.itemCount -= number;
        globalData.points -= points;
    }

    cost -= points;

    if(currentRoster) {
        currentRoster->amountPoints(0.0 - points);
        currentRoster->emitItemRemoved(const_cast<RosterUnit*>(this), itemKey, number);
    }
    return true;
}

void RosterUnit::removeItem(const QString& itemKey) {
    subtractItem(itemKey, itemsInUnit.value(itemKey, 0));
}

UnitOptions_t RosterUnit::getActivatedOptions() const {
    return activatedOptions;
}

bool RosterUnit::isOptionActivated(const QString &optionKey) const {
    return activatedOptions.contains(optionKey);
}

bool RosterUnit::activateOption(const QString &optionKey) {
    Q_ASSERT(_units);

    Unit unit(_units->get(_key));

    // Do nothing if option is locked or already activated
    if(optionLockedStatus.value(optionKey) > 0 || activatedOptions.contains(optionKey))
        return false;

    // Check if this option really exists
    if(!unit.options.contains(optionKey))
        return false;

    // All right, activate it!

    // Perform actions
    UnitOption opt = unit.options.get(optionKey);

    for(int i = 0; i<opt.actions.size(); ++i)
        opt.actions.at(i)->doAction(this);

    activatedOptions << optionKey;
    float optCost = opt.getCost();
    cost += optCost; // Modify unit cost
    if(currentRoster) {
        currentRoster->amountPoints(optCost);
        currentRoster->emitOptionActivated(const_cast<RosterUnit*>(this), optionKey);
    }
    return true;
}

bool RosterUnit::deactivateOption(const QString &optionKey) {
    Q_ASSERT(_units);

    Unit unit(_units->get(_key));

    // Check if it's really activated and it's unlocked
    if(optionLockedStatus.value(optionKey) > 0 || !activatedOptions.contains(optionKey))
        return false;

    // Ensure the option exists
    if(!unit.options.contains(optionKey))
        return false;

    // Undo actions

    UnitOption opt = unit.options.get(optionKey);

    for(int i = opt.actions.size()-1; i>=0; --i)
        opt.actions.at(i)->undoAction(this);

    activatedOptions.remove(optionKey);
    float optCost = opt.getCost();
    cost -= optCost; // Modify unit cost
    if(currentRoster) {
        currentRoster->amountPoints(0.0 - optCost);
        currentRoster->emitOptionDeactivated(this, optionKey);
    }
    return true;
}

bool RosterUnit::isOptionVisible(const QString& key) const {
    return this->optionVisibleStatus.value(key,1) > 0;
}

void RosterUnit::amountOptionVisible(const QString& key, bool visible) {
    if(visible)
        optionVisibleStatus[key] += 1;
    else
        optionVisibleStatus[key] -= 1;
}

bool RosterUnit::isOptionLocked(const QString& key) const {
    return optionLockedStatus.value(key) > 0;
}

void RosterUnit::amountOptionLocked(const QString& key, bool locked) {
    if(locked)
        optionLockedStatus[key] += 1;
    else
        optionLockedStatus[key] -= 1;
}

QString RosterUnit::getCell(const QString& key) const {
    return cells.value(key);
}

void RosterUnit::amountCell(const QString& key, const QString& value, bool forceAppend) {
    for(auto i = linkedUnits.constBegin(); i!= linkedUnits.constEnd(); ++i) {
        RosterUnit *child = i.value();
        if(child->replacesModel)
            child->amountCell(key, value, forceAppend);
    }
    QString &cell = cells[key];
    if(!forceAppend) {
        bool canCast;
        double num;
        num = value.toDouble(&canCast);
        if(canCast) {
            QStringList parts(cell.split('/'));
            num += parts.at(0).toDouble(&canCast);
            if(canCast) {
                parts[0] = QString::number(num);
                cell = parts.join("/");
                if(currentRoster)
                    currentRoster->forceUpdate(this);
                return;
            }
        }
    }
    // Append if was not able to cast both values
    cell.append("/" % value);
    if(currentRoster)
        currentRoster->forceUpdate(this);
}

void RosterUnit::subtractCell(const QString& key, const QString& value, bool forcedAppend) {
    for(auto i = linkedUnits.constBegin(); i!= linkedUnits.constEnd(); ++i) {
        RosterUnit *child = i.value();
        if(child->replacesModel)
            child->subtractCell(key, value, forcedAppend);
    }
    QString &cell = cells[key];
    if(!forcedAppend) {
        bool canCast;
        double num;
        QStringList parts(cell.split('/'));
        num = parts.at(0).toDouble(&canCast);
        if(canCast) {
            num -= value.toDouble(&canCast);
            if(canCast) {
                parts[0] = QString::number(num);
                cell = parts.join("/");
                if(currentRoster)
                    currentRoster->forceUpdate(this);
                return;
            }
        }
    }
    // Delete "/value" if was not able to cast both values
    QString temp("/" %value);
    int i = cell.indexOf(temp);
    if(i > -1)
        cell.replace(i, temp.size(), "");
    if(currentRoster)
        currentRoster->forceUpdate(this);
}

ItemLimits_t RosterUnit::getItemLimits() const {
    return itemLimits;
}

void RosterUnit::amountItemLimits(const CategoryDataHash& newValue) {
    // \TODO: Implement amountItemLimits()
}

void RosterUnit::subtractItemLimits(const CategoryDataHash& newValue) {
    // \TODO: Implement subtractItemLimits()
}

void RosterUnit::addLinkedChild(const QString& unitKey, bool replacesModel) {
    RosterUnit *child = NULL;
    if(!linkedUnits.contains(unitKey)) {
        child = new RosterUnit(unitKey, this);
        child->replacesModel = replacesModel;
        Unit unit(_units->get(_key));
        for(auto i = activatedOptions.constBegin(); i!=activatedOptions.constEnd(); ++i) {
            UnitOption opt = unit.options.get(*i);
            for(int j = 0; j<opt.actions.size(); ++j) {
                if(opt.actions.at(j)->type() != ObjectAction::ModifyCell)
                    continue;
                opt.actions.at(j)->doAction(child);
            }
        }
        if(currentRoster)
            currentRoster->forceUpdate(this);
    }
    linkedUnits.insertMulti(unitKey, child);
}

void RosterUnit::removeLinkedChild(const QString& unitKey) {
    delete linkedUnits.take(unitKey);
    if(currentRoster)
        currentRoster->forceUpdate(this);
}

void RosterUnit::setCurrentItemCollection(const DualCollection<GameItem> *items) {
    _items = items;
}

void RosterUnit::setCurrentUnitCollection(const UnitCollection *units) {
    _units = units;
}

void RosterUnit::deleteNonAllowedItems() {
    // Private function
    for(UnitItems_t::const_iterator i = itemsInUnit.constBegin();
        i!= itemsInUnit.constEnd(); ++i) {
        if(!isAllowedItem(i.key())) {
            removeItem(i.key());
        }
    }
}
